package pipeline;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Hashtable;
import java.util.List;
import java.util.Random;
import java.util.Map.Entry;

import joint.feature.EventTypeFeature;
import joint.feature.Feature;
import joint.feature.LinkDepFeature;
import util.io.FileUtil;
import def.ArgType;
import def.Argument;
import def.BioNLPDocument;
import def.Event;
import def.EventType;
import def.Protein;
import def.TSpan;
import def.TextType;
import exp.Count;

public class FinalPrediction {
	public static void main(String[] args) throws Exception{
		BufferedReader reader = new BufferedReader(new FileReader("log"));
		String line = null;
		Hashtable<String, Integer> id = new Hashtable<String, Integer>();
		System.err.println("reading....");
		while ((line = reader.readLine()) != null){
			if (line.startsWith("[EXAMPLE]")){
				String[] items = line.substring(line.indexOf("]")+1).split(":");
				for (int i = 0; i < items.length-1; i++){
					String key = items[i].substring(items[i].indexOf(" ")+1);
					if (!id.containsKey(key))
						id.put(key, id.size()+1);
				}
			}
		}
		reader.close();
		System.err.println("printing dictionary ... ");
		PrintWriter pw = new PrintWriter("rank.dict");
		for (String key: id.keySet()){
			pw.println(key+"\t"+id.get(key));
		}
		pw.close();
		System.err.println("translating...");
		reader = new BufferedReader(new FileReader("log"));
		pw = new PrintWriter("ranks");
		while ((line = reader.readLine()) != null){
			if (line.startsWith("[EXAMPLE]")){
				String[] items = line.substring(line.indexOf("]")+1).split(":");
				String val = items[0].substring(0, items[0].indexOf(" "));
				pw.print(val);
				String key = items[0].substring(items[0].indexOf(" ")+1);
				Hashtable<Integer, String> fs = new Hashtable<Integer, String>();
				for (int i = 1; i < items.length-1; i++){
					val = items[i].substring(0, items[i].indexOf(" "));
					fs.put(id.get(key), val);
					key = items[i].substring(items[i].indexOf(" ")+1);
				}
				fs.put(id.get(key), items[items.length-1]);
				ArrayList<Integer> sort = new ArrayList<Integer>(fs.keySet());
				Collections.sort(sort);
				for (int i = 0; i < sort.size(); i++){
					pw.print(" "+sort.get(i)+":"+fs.get(sort.get(i)));
				}
				pw.println();
			}
		}
		pw.close();
		reader.close();
	}
	public static ArrayList<Feature> features = new ArrayList<Feature>();
	public static TriggerDetection td = new TriggerDetection();
	static {
		td.init();
		features.add(new EventTypeFeature());
		features.add(new LinkDepFeature());
	}

	public static List<Hashtable<String, Integer>> examples = null;
	public static ArrayList<Event> truthEvents = null;
	public static ArrayList<Float>scores = null;
	public static BioNLPDocument doc = null;
	// don't forget normalization
	// should include:
	// - dep, dep for the same tspan
	// - trigger being head?
	// - event class?
	// - hoifung's ling-motivated rules
	// constratins:
	// - adjacent proteins linked to the same event?
	// - their, its, ... coref?
	public static void buildFeatures(ArrayList<Event> pred, BioNLPDocument document, int sentId, ArrayList<Event> truth){
		// recursively assign event types
		truthEvents = truth;
		examples = new ArrayList<Hashtable<String, Integer>>();
		doc = document;
		scores = new ArrayList<Float>();
		assignEventType(pred, 0,  sentId );

	}

	public static void assignEventType(ArrayList<Event> pred, int level,  int sentId){
		// base case: call generate features
		if (pred.size() == level){
			generateFeatures(pred, sentId);
		}
		else{
			Event event = pred.get(level);
			boolean hasEventArg = false, hasMultiple = false;
			for (int i = 0; i < event.args.size() ; i++){
				Argument arg  = event.args.get(i);
				if (arg instanceof Event){
					hasEventArg = true;
					break;
				}
				if (event.types.get(i) ==  ArgType.Cause){
					hasEventArg = true;
					break;
				}
			}
			if (!hasEventArg && event.args.size() > 1){
				hasMultiple = true;
			}
			ArrayList<EventType> candidates = new ArrayList<EventType>();
			for (EventType type: EventType.values()){
				if (hasEventArg){
					if (type == EventType.Positive_regulation || type == EventType.Negative_regulation || type == EventType.Regulation){
						// dictionary
						event.eventType = type;
						if (td.freq.get(type.toString()).containsKey(event.tspan.text)){
							candidates.add(type);
						}
					}
				}
				else if (hasMultiple){
					if (type == EventType.Binding){
						candidates.add(type);
					}
				}
				else{
					//dictionary
					if (td.freq.get(type.toString()).containsKey(event.tspan.text)){
						candidates.add(type);
					}
				}
			}
			if (candidates.size() == 0){
				if (hasEventArg){
					candidates.add(EventType.Positive_regulation );
					candidates.add(EventType.Negative_regulation );
					candidates.add(EventType.Regulation );
				}
				else{
					Random r = new Random(57849);
					EventType[] types = EventType.values();
					// randomly picked some
					for (int j = 0; j < 3; j++){
						int idx = r.nextInt(types.length);
						while (candidates.contains(types[idx])){
							idx = r.nextInt(types.length);
						}
						candidates.add(types[idx]);
					}
				}
			}
			for (EventType type: candidates){
				event.eventType = type;
				assignEventType(pred, level+1,  sentId );
			}
		}
	}

	public static void generateFeatures(ArrayList<Event> pred,  int sentId){
		// event type features
		// including word,


		// link features
		// dep


		// TODO constraints
		// 1. p1 and p2
		// 2. prefix dep path
		// 3. anaphoric expression

		Hashtable<String, Integer> example = new Hashtable<String, Integer>();
		for (Feature feature: features){
			feature.apply(example, pred, doc, sentId);
		}

		scores.add(eval(pred));
		examples.add(example);
	}


	private static float eval(ArrayList<Event> current) {
		// with truthEvents
		int fp = current.size();


		Count c = new Count();
		//		HashSet<Event> tpInPred = new HashSet<Event>(), tpInTruth = new HashSet<Event>();
		//		HashSet<Event> truths = new HashSet<Event>();
		//		HashSet<String> truthString = new HashSet<String>();
		//		for (Event e:  document.events.values()){
		//			if (text.getSentenceIdByPos(e.tspan.startIdx)!=line) continue;
		//			String str = e.toString();
		//			str = str.substring(str.indexOf("\t"));
		//			if (!truthString.contains(str)){
		////				truths.add(e);
		//				truthString.add(str);
		//			}
		//		}
		for (Event e:truthEvents){
			c.all++;
			//			Event found = null;
			for (Event pe: current){
				if (match(pe, e)){
					//				if (tpInPred.contains(pe)) continue;
					fp--;
					c.num++;
				}
			}
		}
		c.fp += fp;
		//		System.out.println("[EVAL] "+document.fileId + "@"+ line+"  "+c.num+" "+c.fp+" "+c.all+" "+current);


		float f1 = 0;
		f1 += 1+(float)c.fp / c.num + (float)c.all / c.num;
		f1 = 2/f1;
		return f1;
	}

	private static  boolean match(Event pe, Event e){
		boolean res = true;
		if (pe.tspan.matchTruth(e.tspan, doc)&& pe.eventType.equals( e.eventType)){
			//			System.err.println("[EVAL TRUTH]"+e);
			//			System.err.println("[EVAL PRED]"+pe);
			// FIXME consider Site arguments TODO
			// NOTE: forget about the event types for now
			int match = -1;
			if (e.args.size() == pe.args.size()){
				match = 0;
				for (int j = 0; j < e.args.size(); j++){
					for (int k = 0; k < pe.args.size(); k++){
						if (e.args.get(j) instanceof Protein){
							if (pe.args.get(k) instanceof Protein){
								if (e.args.get(j).equals(pe.args.get(k))&&e.types.get(j).match(pe.types.get(k))){
									match++;		
								}
							}
						}
						else if (e.args.get(j) instanceof Event){
							if(pe.args.get(k) instanceof Event){
								if (match((Event)pe.args.get(k), (Event)e.args.get(j)) &&e.types.get(j).match(pe.types.get(k))){
									match++;
								}
							}
						}
					}
				}
			}
			else{
				match = -1;
			}
			//			System.err.println("[EVAL EQUAL] "+(match ==  e.args.size())+" " +  match);
			if (match ==  e.args.size()){
				//				if (found!=null){
				//					//					System.err.println("already matched this "+e+" ---with--- "+found+"\n***but "+pe);
				//					break;
				//				}
				//				tpInPred.add(pe);
				//				tpInTruth.add(e);
				//				c.num ++;
				//				fp -- ;
				//				found =pe;
				res = true;
				//						break;
			}
			else{
				res = false;
			}
		}
		else{
			res = false;
		}
		return res;
	}


	public static void saveToBioNLPFormat(BioNLPDocument doc, ArrayList<Event> events, String filename, Hashtable<String, Protein> corefProteins){
		int max = 0;
		// get the id number to start (after protein ids)
		for (String str: doc.tspans.keySet()){
			if (str.startsWith("T")&&doc.tspans.get(str).tspan.textType== TextType.Protein){
				int i = Integer.parseInt(str.substring(1));
				if (i > max){
					max = i;
				}
			}
		}

		// assign tid to triggers
		Hashtable<TSpan, Integer> tspans = new Hashtable<TSpan, Integer>(); 
		for (Event e: events){
			if (e.eventType == null)
				continue;
			if (!tspans.containsKey(e.tspan) ){
				tspans.put(e.tspan, max+1+tspans.size());
			}
		}

		StringBuilder sb = new StringBuilder();
		// print tspans
		for (TSpan key: tspans.keySet()){
			sb.append("T"+tspans.get(key)+ "\t"+key.textType+" "+key.startIdx +" "+key.endIdx +"\t"+key.text+"\n");
		}

		// print events
		Integer ei = 0;
		Hashtable<Event, Integer> eventMap = new Hashtable<Event,Integer>();
		// ??
		ArrayList<String> toReplace = new ArrayList<String>(), replacement = new ArrayList<String>();
		for (Event e: events){
			if (e.eventType == null)
				continue;
			String line = getArgStringBioNLPFormat(e, tspans, /*ei,*/ doc, eventMap, corefProteins);

			if (line!=null){
				String[] sublines = line.split("\n");
				for (int j = 0; j < sublines.length; j++){
					if (sublines[j].trim().length()!=0){
						int idx = sb.toString().indexOf(sublines[j].substring(sublines[j].indexOf("\t")));
						if (idx == -1){
							// previous text does not contain the same event description
							sb.append(sublines[j]+"\n");
						}
						else{
							// find the id (trueId) of the previous same event description
							// replace all the following ids (falseId) with (trueId) 
							int startIdx = sb.toString().indexOf("\n", idx -6);
							int trueId = Integer.parseInt(sb.toString().substring(startIdx+2, idx));
							int falseId = Integer.parseInt(sublines[j].substring(1, sublines[j].indexOf("\t")));
							for (Entry<Event, Integer> entry: eventMap.entrySet()){
								if (entry.getValue() == falseId){
									eventMap.put(entry.getKey(), trueId);
									break;
								}
							}
							toReplace.add("E"+falseId);
							replacement.add("E"+trueId);
						}
					}
				}
			}
		}
		String text = sb.toString().replaceAll("\n+", "\n")+"\n";
		for (int i = 0; i < toReplace.size(); i++){
			System.err.println(doc.fileId+" replacing .."+toReplace.get(i)+"  "+ replacement.get(i));
			text = text.replace(toReplace.get(i)+" ", replacement.get(i)+" ");
			text = text.replace(toReplace.get(i)+"\n", replacement.get(i)+"\n");
		}
		List<String> lines = Arrays.asList(text.split("\n"));
		Collections.sort(lines, new Comparator<String>(){
			@Override
			public int compare(String o1, String o2) {
				if (o1.length() <1){
					return 1;
				}
				if (o2.length() <1){
					return -1;
				}
				String t1 = o1.substring(0, o1.indexOf("\t")).replace("T", "").replace("E", "9");
				String t2 = o2.substring(0, o2.indexOf("\t")).replace("T", "").replace("E", "9");
				
				return Integer.parseInt(t1)-Integer.parseInt(t2);
			}
		});
		FileUtil.writeTextToFile(text.substring(0, text.length()-1), filename);
	}

	public static String getArgStringBioNLPFormat(Argument arg0, Hashtable<TSpan, Integer> tspans, /*Integer ei,*/ BioNLPDocument doc, Hashtable<Event, Integer> eventMap,Hashtable<String, Protein> corefProteins){
		if (arg0 instanceof Protein){
			return null;
		}
		else if (arg0 instanceof Event){
			Event e = (Event)arg0;
			if (e.eventType == null)
				return null;
			System.err.println("[ARG_STRING]"+arg0);
			StringBuilder sb = new StringBuilder();

			if (eventMap.containsKey(e)){
				System.err.println("[ARG_STRING] quits because already done "+arg0);
				return null;
			}
			for (Argument arg: e.args){
				String s = getArgStringBioNLPFormat(arg, tspans/*, ei*/, doc, eventMap, corefProteins);
				if (s != null && s.length()!=0)
					sb.append(s+"\n"); 
			}
			int ei = eventMap.size()+1;
			StringBuilder lineBuilder = new StringBuilder();
			lineBuilder.append("E"+ei+"\t"+e.eventType+":T"+tspans.get(e.tspan));
			for (int j  = 0; j < e.args.size(); j++){
				if (e.args.get(j) instanceof Event){
					boolean eventFound = false;
					for (Event ee : eventMap.keySet()){
						if (ee == e.args.get(j)){
							lineBuilder.append(" "+e.types.get(j)+":E"+eventMap.get(e.args.get(j)));
							eventFound = true;
							break;
						}
					}
					if (!eventFound){
						System.err.println("[FINAL_FORMAT] cant find the arg event "+e.args.get(j)+" @ "+e);
					}
				}
				else if (e.args.get(j) instanceof Protein){
					String pid = e.args.get(j).tspan.tid;
					if (corefProteins.containsKey(pid)){
						pid = corefProteins.get(pid).tspan.tid;
					}
					lineBuilder.append(" "+e.types.get(j)+":"+pid);
				}
				else{
					System.err.println("[SAVE_BIONLP_OUPUT]"+e.args.get(j)+" "+doc.fileId+" not a protein nor an event");
				}
			}
			//			ei;
			if (lineBuilder.toString().contains("Theme")){
				sb.append(lineBuilder);
				eventMap.put(e, ei);
			}
			else{
				System.err.println("[FINAL_FORMAT] failed line: "+ lineBuilder.toString());
			}
			return sb.toString();
		}
		else{
			return null;
		}
	}
}
